package ${package}.repository;

import com.google.common.collect.Lists;
import com.ishansong.common.model.Paging;
import com.ishansong.log.Loggers;
import com.ishansong.dao.common.Dao;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;

import javax.persistence.Table;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collections;
import java.util.List;

/**
 */
public abstract class BaseRepository<T> implements BaseDataSource {

    protected final Class generic =
            (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];

    protected final String table = AnnotationUtils.findAnnotation(generic, Table.class).name();

    protected final String PAGING_LIST_SQL = "SELECT * FROM " + table + " WHERE 1=1 ";
    protected final String PAGING_COUNT_SQL = "SELECT COUNT(1) FROM " + table + " WHERE 1=1";

    private Logger log = Loggers.get(BaseRepository.class);

    @Autowired
    protected Dao<T> dao;

    @Override
    public String getDs() {
        return "orderdb";
    }

    public Long save(T t){
        return dao.save(t);
    }

    public List<T> save(List<T> ts){
        return dao.save(ts);
    }

    public Boolean update(T t){
        return dao.update(t);
    }

    /**
     * 更新操作，当需要被更新的字段为null时，调用此方法
     * @param t
     * @return
     */
    public Boolean updateContainsNull(T t){
        return dao.updateContainsNull(t);
    }

    public T get(Long id){
        return (T)dao.get(generic, id);
    }

    public List<T> get(List<? extends Serializable> ids){
        return dao.getList(generic, ids);
    }

    public Boolean delete(Long id){
        return dao.delete(generic, id);
    }

    public Boolean delete(List<Long> ids){
        return dao.deleteList(generic, ids);
    }


    protected T queryObjectByMethod(String methodName, Object[] objects){

        List<Serializable> ids = dao.getIdList(methodName,objects,0,1);

        if(!CollectionUtils.isEmpty(ids)){
            return get((Long)ids.get(0));
        }

        return null;
    }

    protected List<T> queryListsByMethod(String methodName, Object[] objects, Integer... params){

        List<Serializable> ids;

        if(null != params && params.length > 1){

            ids = dao.getIdList(methodName,objects,params[0],params[1]);

        }else{

            ids = dao.getIdList(methodName,objects);
        }

        if(!CollectionUtils.isEmpty(ids)){

            return dao.getList(generic,ids);
        }

        return Lists.newArrayList();

    }


    protected Integer countListsByMethod(String methodName, Object[] objects){

        return dao.count(methodName,objects);
    }

    public Integer pagingCount(Criterias criterias){

        PagingQuery query = buildSql(criterias.build(),null);
        log.info("pagingCountSql:"+query.criteria);
        Integer total = dao.countSql(getDs(), PAGING_COUNT_SQL + query.criteria, query.params);
        return total;
    }

    public Paging<T> paging(Integer offset, Integer limit){
        return paging(EMPTY_CRITERIAS, EMPTY_ORDERS, offset, limit);
    }

    public Paging<T> paging(Criterias criterias, Integer offset, Integer limit){
        return paging(criterias, EMPTY_ORDERS, offset, limit);
    }

    public Paging<T> paging(Criterias criterias, Orders orders, Integer offset, Integer limit){
        PagingQuery query = buildSql(criterias.build(), orders.build());
        log.info("pagingCountSql:"+PAGING_COUNT_SQL+query.criteria);
        log.info("pagingSql:"+PAGING_LIST_SQL+query.criteria+query.order);
        Integer total = dao.countSql(getDs(), PAGING_COUNT_SQL + query.criteria, query.params);
        if (total <= 0){
            return Paging.empty();
        }
        List<T> datas = dao.listSql(getDs(), PAGING_LIST_SQL + query.criteria + query.order, query.params, generic, offset, limit);
        return new Paging<>(Long.valueOf(total), datas);
    }

    protected PagingQuery buildSql(List<Criteria> criterias, List<COrder> orders){
        StringBuilder criteriaSQL = new StringBuilder();
        List<Object> params = Collections.emptyList();
        if (!CollectionUtils.isEmpty(criterias)){
            params = Lists.newArrayListWithExpectedSize(criterias.size());
            for (Criteria criteria : criterias){
                criteriaSQL.append(" AND ").append(criteria.name);
                if (CriteriaType.AND == criteria.type){
                    criteriaSQL.append(" = ?");
                    params.add(criteria.value);
                } else if(CriteriaType.GT == criteria.type){
                    criteriaSQL.append(" > ?");
                    params.add(criteria.value);
                } else if(CriteriaType.GE == criteria.type){
                    criteriaSQL.append(" >= ?");
                    params.add(criteria.value);
                } else if(CriteriaType.LT == criteria.type){
                    criteriaSQL.append(" < ?");
                    params.add(criteria.value);
                } else if(CriteriaType.LE == criteria.type){
                    criteriaSQL.append(" <= ?");
                    params.add(criteria.value);
                } else if (CriteriaType.IN == criteria.type){
                    List datas = (List)criteria.value;
                    criteriaSQL.append(" IN (");
                    for (Object o : datas){
                        criteriaSQL.append("?,");
                        params.add(o);
                    }
                    criteriaSQL.deleteCharAt(criteriaSQL.length() - 1);
                    criteriaSQL.append(")");
                }
            }
        }

        StringBuilder orderSQL = new StringBuilder();
        if (!CollectionUtils.isEmpty(orders)){
            orderSQL.append(" ORDER BY ");
            for (COrder order : orders) {
                orderSQL.append(order.name);
                if (order.type == OrderType.DESC){
                    orderSQL.append(" DESC");
                } else {
                    orderSQL.append(" ASC");
                }
                orderSQL.append(",");
            }
            orderSQL.deleteCharAt((orderSQL.length() - 1));
        }

        PagingQuery query = new PagingQuery();
        query.criteria = criteriaSQL.toString();
        query.order = orderSQL.toString();
        query.params = params.toArray();
        return query;
    }

    protected static class PagingQuery{
        public String criteria;
        public String order;
        public Object[] params;
    }

    private static final Criterias EMPTY_CRITERIAS = Criterias.create();

    protected static class Criterias {

        private List<Criteria> criterias = Lists.newArrayList();

        public static Criterias create(){
            return new Criterias();
        }

        public Criterias and(String name, Object value){
            criterias.add(Criteria.newCriteria(name, value, CriteriaType.AND));
            return this;
        }

        public Criterias gt(String name, Object value){
            criterias.add(Criteria.newCriteria(name, value, CriteriaType.GT));
            return this;
        }

        public Criterias ge(String name, Object value){
            criterias.add(Criteria.newCriteria(name, value, CriteriaType.GE));
            return this;
        }

        public Criterias lt(String name, Object value){
            criterias.add(Criteria.newCriteria(name, value, CriteriaType.LT));
            return this;
        }

        public Criterias le(String name, Object value){
            criterias.add(Criteria.newCriteria(name, value, CriteriaType.LE));
            return this;
        }

        public Criterias in(String name, List<?> values){
            criterias.add(Criteria.newCriteria(name, values, CriteriaType.IN));
            return this;
        }

        public List<Criteria> build(){
            return this.criterias;
        }
    }

    protected Integer queryPartnerNoByMethod(String methodName, Object[] objects){

        return dao.count(methodName,objects);
    }

    protected static class Criteria{

        public String name;

        public Object value;

        public CriteriaType type;

        public static Criteria newCriteria(String name, Object value, CriteriaType type){
            Criteria c = new Criteria();
            c.name = name;
            c.value = value;
            c.type = type;
            return c;
        }
    }

    protected enum CriteriaType {
        AND, IN, LT, LE, GT, GE
    }

    private static final Orders EMPTY_ORDERS = Orders.create();

    protected static class Orders {

        private List<COrder> orders = Lists.newArrayList();

        public static Orders create(){
            return new Orders();
        }

        public Orders asc(String name){
            orders.add(COrder.newOrder(name, OrderType.ASC));
            return this;
        }

        public Orders desc(String name){
            orders.add(COrder.newOrder(name, OrderType.DESC));
            return this;
        }

        public List<COrder> build(){
            return this.orders;
        }
    }


    protected static class COrder{

        public String name;

        public OrderType type;

        public static COrder newOrder(String name, OrderType type){
            COrder c = new COrder();
            c.name = name;
            c.type = type;
            return c;
        }
    }

    protected enum OrderType {
        ASC, DESC
    }



}
